﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace GameDataTempshitEditor.Widgets
{
    public abstract class PropertyItemEditor
    {
        private readonly PropertyItemBase _Item;

        public delegate void ItemEditorHandler( object sender );
        public event ItemEditorHandler OnValueCached;

        public PropertyItemEditor( PropertyItemBase Item )
        {
            _Item = Item;
        }

        public bool IsReadOnly
        {
            get
            {
                return _Item.IsReadOnly();
            }
        }

        public bool ApplyValue( string value )
        {
            string errorMessage = "";
            if ( !ApplyValue( value, ref errorMessage ) )
            {
                ErrorBox.ShowError( String.Format( "Error setting value for property {0}:\r\n", _Item.Name ) + errorMessage );
                return false;
            }

            return true;
        }

        public virtual bool ValidateValue( string value, ref string errorMessage )
        {
            return true; // validated always
        }

        public bool ApplyValue( string value, ref string errorMessage )
        {
            if ( IsReadOnly )
            {
                errorMessage = "Cannot change read only values"; ;
                return false;
            }

            if ( !ValidateValue( value, ref errorMessage ) )
                return false;

            if ( !_Item.SetValue( this, value, ref errorMessage ) )
                return false;

            return true;
        }

        public void ActionExecuted()
        {
            if ( OnValueCached != null )
                OnValueCached( this );
        }

        public void RefreshPlacement( int totalButtonWidth )
        {
            var localRect = _Item.ValueRect;
            localRect.Width -= totalButtonWidth;
            RefreshPlacement( localRect );
        }

        public abstract bool DestroyControls( bool cancelEditMode );
        public abstract void RefreshPlacement( Rectangle valueRect );
        public abstract Control CreateControls();
    }

    // text editor (default)

    public class PropertyItemEditor_Text : PropertyItemEditor
    {
        protected string _EditValue;
        protected TextBox _TextBox;

        public PropertyItemEditor_Text( PropertyItemBase Item )
            : base( Item )
        {
            _EditValue = Item.DisplayValue();
        }

        public override bool DestroyControls( bool cancelEditMode )
        {
            if ( _TextBox != null )
            {
                if ( !cancelEditMode )
                {
                    CopyTextToProperty();
                }

                _TextBox.Parent.Controls.Remove( _TextBox );
                _TextBox = null;
            }

            return true;
        }

        public override Control CreateControls()
        {
            if ( _TextBox == null )
                _TextBox = new TextBox();

            _TextBox.Text = _EditValue;
            _TextBox.BorderStyle = BorderStyle.None;
            //_TextBox.AutoSize = false;
            _TextBox.ReadOnly = IsReadOnly;
            _TextBox.AcceptsReturn = true;
            _TextBox.SelectAll();

            _TextBox.KeyDown += _TextBox_KeyDown;


            return _TextBox;
        }

        void _TextBox_KeyDown( object sender, KeyEventArgs e )
        {
            // TODO: move the input filtering to the control
            if ( e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.PageDown || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.Escape )
            {
                ( (DataPropertyGrid)_TextBox.Parent ).HandleKey( e.KeyCode );
                e.Handled = true;
            }

            if ( e.KeyCode == Keys.Enter )
            {
                CopyTextToProperty();
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
        }

        public override void RefreshPlacement( Rectangle valueRect )
        {
            if ( _TextBox != null )
            {
                _TextBox.Left = valueRect.X;
                _TextBox.Top = valueRect.Y + ( valueRect.Height - _TextBox.Height ) / 2;
                _TextBox.Width = valueRect.Width;
            }
        }

        void CopyTextToProperty()
        {
            if ( _TextBox.Text != _EditValue )
            {
                if ( !ApplyValue( _TextBox.Text ) )
                {
                    _TextBox.Text = _EditValue;
                    _TextBox.SelectAll();
                    _TextBox.Focus();
                }
                else if ( _TextBox != null )
                {
                    _EditValue = _TextBox.Text;
                    ActionExecuted();
                }
            }
        }
    }

    // int based editor - accepts only number, can be in range
    public class PropertyItemEditor_Int : PropertyItemEditor_Text
    {
        public PropertyItemEditor_Int( PropertyItemBase Item, int rangeMin = int.MinValue, int rangeMax = int.MaxValue )
            : base( Item )
        {
            RangeMin = rangeMin;
            RangeMax = rangeMax;
        }

        public override bool ValidateValue( string value, ref string errorMessage )
        {
            int parsed = 0;
            if ( !int.TryParse( value, out parsed ) )
            {
                errorMessage = "Value is not a valid integer";
                return false;
            }

            if ( parsed < RangeMin || parsed > RangeMax )
            {
                errorMessage = "Value is outside allowed range [" + RangeMin.ToString() + " - " + RangeMax.ToString() + "]";
                return false;
            }

            return true;
        }


        private int RangeMin;
        private int RangeMax;
    }

    // float based editor - accepts only number, can be in range
    public class PropertyItemEditor_Float : PropertyItemEditor_Text
    {
        public PropertyItemEditor_Float( PropertyItemBase Item, float rangeMin = float.MinValue, float rangeMax = float.MaxValue )
            : base( Item )
        {
            RangeMin = rangeMin;
            RangeMax = rangeMax;
        }

        public override bool ValidateValue( string value, ref string errorMessage )
        {
            float parsed = 0;
            if ( !float.TryParse( value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out parsed ) )
            {
                errorMessage = "Value is not a valid numerical value";
                return false;
            }

            if ( parsed < RangeMin || parsed > RangeMax )
            {
                errorMessage = "Value is outside allowed range [" + RangeMin.ToString() + " - " + RangeMax.ToString() + "]";
                return false;
            }

            return true;
        }

        private float RangeMin;
        private float RangeMax;
    }

    // names validation
    public class PropertyItemEditor_Name : PropertyItemEditor_Text
    {
        public PropertyItemEditor_Name( PropertyItemBase Item )
            : base( Item )
        {
        }

        static bool IsNameChar( int ch )
        {
            if ( ch >= 'a' && ch <= 'z' )
                return true;

            if ( ch >= 'A' && ch <= 'Z' )
                return true;

            if ( ch >= '0' && ch <= '9' )
                return true;

            // other special allowed characters
            if ( ch == '_' || ch == '#' || ch == '$' || ch == '@' || ch == '+' || ch == '-' )
                return true;

            return false;
        }

        public override bool ValidateValue( string value, ref string errorMessage )
        {
            for ( int i = 0; i < value.Length; ++i )
            {
                int ch = value[i];
                if ( ch <= 32 || ch > 127 || !IsNameChar( ch ) )
                {
                    errorMessage = String.Format( "Names can contain only following characters: a-z, A-Z, 0-9 and _#$@+-\r\nInvalid character used: '{1}', code: {0}", ch, ( (char)ch ).ToString() );
                    return false;
                }
            }

            return true;
        }
    }


    // boolean on/off

    public class PropertyItemEditor_Bool : PropertyItemEditor
    {
        private CheckBox _CheckBox;
        private bool _EditValue;

        public PropertyItemEditor_Bool( PropertyItemBase Item )
            : base( Item )
        {
            _EditValue = ( "true" == Item.DisplayValue() );
        }

        public override bool DestroyControls( bool cancelEditMode )
        {
            if ( _CheckBox != null )
            {
                if ( !cancelEditMode )
                {
                    CopyTextToProperty();
                }

                _CheckBox.Parent.Controls.Remove( _CheckBox );
                _CheckBox = null;
            }

            return true;
        }

        public override Control CreateControls()
        {
            if ( _CheckBox == null )
                _CheckBox = new CheckBox();

            _CheckBox.Checked = _EditValue;
            _CheckBox.CheckedChanged += StateChanged;
            _CheckBox.Height = 18;
            _CheckBox.Text = _EditValue ? "true" : "false";

            return _CheckBox;
        }

        /*void _CheckBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            // TODO: move the input filtering to the control
            if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.PageDown || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.Escape)
            {
                ((DataPropertyGrid)_CheckBox.Parent).HandleKey(e.KeyCode);
                e.Handled = true;
            }
        }*/

        public override void RefreshPlacement( Rectangle valueRect )
        {
            if ( _CheckBox != null )
            {
                _CheckBox.Left = valueRect.X;
                _CheckBox.Top = valueRect.Y + ( valueRect.Height - _CheckBox.Height ) / 2;
                _CheckBox.Width = valueRect.Width;
            }
        }

        void StateChanged( object obj, EventArgs e )
        {
            CopyTextToProperty();
        }

        void CopyTextToProperty()
        {
            if ( _CheckBox.Checked != _EditValue )
            {
                var stringValue = _CheckBox.Checked ? "true" : "false";
                if ( !ApplyValue( stringValue ) )
                {
                    _CheckBox.Checked = _EditValue;
                }
                else if ( _CheckBox != null )
                {
                    _EditValue = _CheckBox.Checked;
                    _CheckBox.Text = _EditValue ? "true" : "false";
                    ActionExecuted();
                }
            }
        }
    }

    // combo box editor (default)

    public class ComboOption
    {
        public string DisplayName;
        public string Value;

        public ComboOption( string value )
        {
            DisplayName = value;
            Value = value;
        }

        public ComboOption( string displayName, string value )
        {
            DisplayName = displayName;
            Value = value;
        }

        public override string ToString()
        {
            return DisplayName;
        }
    }

    public abstract class PropertyItemEditor_Choice : PropertyItemEditor
    {
        public string _EditValue;
        private ComboBox _ComboBox;

        public PropertyItemEditor_Choice( PropertyItemBase Item )
            : base( Item )
        {
            _EditValue = Item.DisplayValue();
        }

        public override bool DestroyControls( bool cancelEditMode )
        {
            if ( _ComboBox != null )
            {
                if ( !cancelEditMode )
                {
                    CopyTextToProperty();
                }

                _ComboBox.Parent.Controls.Remove( _ComboBox );
                _ComboBox = null;
            }

            return true;
        }

        public abstract void GetOptions( ref List<ComboOption> options );

        public override Control CreateControls()
        {
            if ( _ComboBox == null )
                _ComboBox = new ComboBox();

            var options = new List<ComboOption>();
            GetOptions( ref options );

            foreach ( var option in options )
            {
                _ComboBox.Items.Add( option );

                if ( _EditValue == option.Value )
                    _ComboBox.SelectedItem = option;
            }

            _ComboBox.Text = _EditValue;
            _ComboBox.SelectAll();

            _ComboBox.SelectedIndexChanged += _ComboBox_SelectedIndexChanged;
            _ComboBox.KeyDown += _ComboBox_KeyDown;

            return _ComboBox;
        }

        void _ComboBox_KeyDown( object sender, KeyEventArgs e )
        {
            // TODO: move the input filtering to the control
            if ( e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.PageDown || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.Escape )
            {
                if ( _ComboBox.SelectedText != "" )
                {
                    ( (DataPropertyGrid)_ComboBox.Parent ).HandleKey( e.KeyCode );
                    e.Handled = true;
                }
            }

            // Stop the "Ding!"
            if ( e.KeyCode == Keys.Enter )
            {
                e.Handled = true;
                e.SuppressKeyPress = true;
                return;
            }
        }

        void _ComboBox_SelectedIndexChanged( object sender, EventArgs e )
        {
            CopyTextToProperty();
        }

        public override void RefreshPlacement( Rectangle valueRect )
        {
            if ( _ComboBox != null )
            {
                _ComboBox.Left = valueRect.X;
                _ComboBox.Top = valueRect.Y + ( valueRect.Height - _ComboBox.Height ) / 2;
                _ComboBox.Width = valueRect.Width;
            }
        }

        void CopyTextToProperty()
        {
            if ( _ComboBox != null )
            {
                var selected = (ComboOption)_ComboBox.SelectedItem;
                if ( selected != null )
                {
                    if ( _EditValue != selected.Value )
                    {
                        if ( !ApplyValue( selected.Value ) )
                        {
                            foreach ( ComboOption option in _ComboBox.Items )
                            {
                                if ( option.Value == _EditValue )
                                {
                                    _ComboBox.SelectedItem = option;
                                    _ComboBox.Focus();
                                    break;
                                }
                            }
                        }
                        else
                        {
                            _EditValue = selected.Value;
                            ActionExecuted();
                        }
                    }
                }
            }
        }
    }

}
